/**
 * ЯegExpR - Using jQuery
 * Copyright 2010
 * Released under the MIT and GPL licenses.
 * 
 * Author      : y@s
 * Version     : 0.0.4
 * Published   : 2010-05-07
 * Last Update : 2010-05-10
 */
;(function($, undefined){
var
hasOwn   = Object.prototype.hasOwnProperty,
toString = Object.prototype.toString,
args     = { //current arguments
	pattern : "",
	target  : "",
	order   : "",
	replace : ""
},
success  = false, //try parse RegExp from pattern string.
result   = null,
regexp   = null,
fn       = function (){};

//to html-string
function toHS(str){
	str += "";
	return str.replace( ( new RegExp(/["\s<>&]/g ) ),function(m){
		return "&#x" + ( "000" + ( m.charCodeAt() ).toString(16) ).slice(-4) + ";";
	});
}

//to string
function toS( v ){	
	if( v === null ){
		return "null";
	}
	
	if( v === undefined ){
		return "undefined";
	}
	
	var typ = typeof v;
	
	switch(typ){
		case "boolean":
			return v;
		case "number":
			return isNaN(v) ? "NaN" : v;
		case "string":
			return v === "" ? "(an empty string)" : toHS(v);
		default:
			break;
	}
	
	/*
	if(typ == "boolean"){
		return v;
	}
	
	if(typ == "number"){
		return isNaN(v) ? "NaN" : v;
	}
	
	if(typ == "string"){
		return v === "" ? "(an empty string)" : toHS(v);
	}*/
	
	//return [index]:value...
	if( v instanceof Array ){
		return (function( ary ){
			var i = 0, j = ary.length, rs = [];
			for ( ; i < j ; i++ ) {
				rs[rs.length] = "[" + i + "]:" + toS( ary[i] );
			}
			return rs.join("<br/>");
		})( v );
	}
	
	return v.toString();
}

function padRgihtSpace( s, totalWidth ){
	var len = totalWidth - s.length;
	return s + ( len < 1 ? "" : new Array(len + 1).join("&nbsp;") );
}

function isFunction( o ) {
	return toString.call(o) === "[object Function]";
}

function isEscaped( s ){
	if( !s || s.length == 1 ){//null or undefined or empty string
		return false;
	}
	
	//check match \.
	if( ( new RegExp(/^\\[\s\S]$/) ).test( s.slice(-2) ) ){
		//\. or [^\]\.
		if( s.length === 2 || s.charAt(s.length - 3) !== "\\" ){
			return true;
		}
		return arguments.callee( s.slice(0, -2) );
	}
	
	return false;
}


/**
 * @param {Object} args
 * @param {regexp} regexp
 * @param {array}  result
 */
function logger( args, regexp, result ){

	var
	ret = [],
	flg = [],
	retProps = ["length", "input", "index"],
	rgxFlags = ["global", "ignoreCase", "multiline"],
	syntax = {
		match  : "string.match( regexp )",
		replace: "string.replace( regexp, replace-string )",
		test   : "regexp.test( string )",
		exec   : "regexp.exec( string )",
		search : "string.search( regexp )",
		split  : "string.split( regexp )"
	},
	pushRet = function( k, v ){
		ret[ret.length] = {
			key  : k,
			value: v
		};
	}, i, tmp;
	
	
	//current params
	for( i in args ){
		if ( hasOwn.call(args, i) ) {
			pushRet( i, toHS(args[i]) );
		}
	}
	
	//syntax
	pushRet( "syntax", syntax[ args["order"] ] );
	
	
	// -=-=-=- regexp -=-=-=-
	pushRet( "regexp", toHS(regexp) );
	pushRet( "regexp.source", toHS(regexp.source) );
	pushRet( "regexp.lastIndex", toS(regexp.lastIndex) );
	//ret.push({key:"regexp.lastMatch", value:regexp.lastMatch || "-"});
	//ret.push({key:"regexp.leftContext", value:regexp.leftContext || "-"});
	//ret.push({key:"regexp.rightContext", value:regexp.rightContext || "-"});
	//ret.push({key:"regexp.lastParen", value:regexp.lastParen || "-"});
	//ret.push({key:"regexp.index", value:regexp.index || "-"});
	
	
	// -=-=-=- flag -=-=-=-
	i = rgxFlags.length;
	
	while ( i-- ) {
		tmp = rgxFlags[i];
		flg[flg.length] = padRgihtSpace( tmp.slice(0,1) + "\x20(" + tmp + ")", 15 ) + ": " + regexp[tmp];
	}
	
	pushRet( "flag", flg.sort().join("<br>") );
	
	
	// -=-=-=- result -=-=-=-
	pushRet( "result", toS(result) );
	
	
	// -=-=-=- result type -=-=-=-
	pushRet( "result type", (function(r){
		if ( r === null ){
			return "null";
		}
		if( r === undefined ){
			return "undefined";
		}
		
		var typ = typeof r;
		
		if(typ == "boolean"){
			return "boolean";
		}
		if(typ == "number"){
			return "number" + (isNaN(r) ? "(NaN)" : "");
		}
		if(typ == "string"){
			return "string";
		}
		
		if( r instanceof Array ){
			return "array";
		}
		
		return "what's type?";
	})(result) );
	
	
	// result : length, input, index
	if ( args.order === "exec" && result !== null /*&& result !== undefined*/ ) {
		i = retProps.length;
		while (i--) {
			pushRet( "result." + retProps[i], toHS( result[ retProps[i] ] ) );
		}
	}
	
	return ret;
};

fn.prototype = {
	/**
	 * @param params
	 * 		- pattern {string} - regexp pattern string
	 * 		- target  {string} - target string
	 * 		- order   {string} - function order (match, replace, test, exec, search, split)
	 * 		- replace {string} - replace string
	 * @return depend on order
	 */
	check:function( params ){
		
		//cache current arguments.
		//args = params ×
		args = {
			pattern: params.pattern || "",
			target : params.target  || "",
			order  : params.order   || "",
			replace: params.replace || ""
		};
		
		//is regexp pattern?
		//cache success
		success = this.test(params.pattern);
		
		if ( success === true ) {
			result = ({
				match: function(rgx, str){
					return str.match(rgx);
				},
				replace: function(rgx, str, r){
					return str.replace(rgx, r);
				},
				test: function(rgx, str){
					return rgx.test(str);
				},
				exec: function(rgx, str){
					return rgx.exec(str);
				},
				search: function(rgx, str){
					return str.search(rgx);
				},
				split: function(rgx, str){
					return str.split(rgx);
				}
			})[args.order](regexp, args.target, args.replace);
		}
		else{
			result = success;
			regexp = null;
			args  = {
				pattern : "",
				target  : "",
				order   : "",
				replace : ""
			};
		}
		
		return result;
	},
	/** is RegExp pattern?
	 * @param  ptrn {string}
	 * @return      {boolean}{string} true or err message string.
	 */
	test:function( ptrn ){
		
		var flg, i, f;
		
		//pattern check
		if( ( new RegExp(/^\/[\s\S]+\/[gim]{0,3}$/) ).test( ptrn ) ){
			
			//flag check
			flg = ( ( new RegExp(/[gim]+$/) ).exec( ptrn ) || [] )[0]; // or undefined
			
			if( flg && flg.length > 1 ){
				
				//Duplicate check
				f = flg.split("").sort();
				i = f.length - 1;
				
				for ( ; i > 0 ; ) {
					if ( f[i] === f[--i] ) {
						return "flags is too much. @validate test";
					}
				}
			}//flag check end
			
			//create regexp object from regexp pattern string.
			try {
				//try parse RegExp
				regexp = this.parseRegExp(ptrn);
			}
			catch(e){
				return e;
			}
		
		}
		else{
			return "Invalid regular expression pattern. @validate test";
		}
		
		return true;
	},
	/** Parse RegExp from string.
	 * @param  s {string} - regexp pattern string
	 * @return   {regexp}
	 */
	parseRegExp: function( s ){
		
		var
		ret  = ( new RegExp(/^\/([\s\S]+)\/([gim]{0,3})$/) ).exec(s),
		sce  = ret[1],
		flag = ret[2] || "",
		i    = 0,
		rgx;
		
		// escape "/" @source
		for( ; i < sce.length ; i++ ){
			if( sce.charAt(i) === "/" && !isEscaped( sce.slice( 0, i + 1 ) ) ){
				sce = sce.slice(0, i) + "\\" + sce.slice(i);
				i++;
			}
		}
		
		try {
			//cache regexp
			rgx = new RegExp(sce, flag);
			
			//debug
			if( rgx.source !== sce ){
				throw "Unmatched pattern";
			}
			if ( rgx.global && !/g/.test(flag) ) {
				throw "Unmatched flag global";
			}
			if ( rgx.ignoreCase && !/i/.test(flag) ) {
				throw "Unmatched flag ignoreCase";
			}
			if ( rgx.multiline && !/m/.test(flag) ) {
				throw "Unmatched flag multiline";
			}
		}
		catch (e) {
			throw e + " @parse RegExp";
		}
		
		return rgx;
	},
	/**
	 * @param callback {function} 
	 */
	log: function(callback){
		callback = isFunction( callback ) ? callback : function(){};
		if ( success !== true ) {
			callback.call(null);
			return;
		}
		callback.call( null, logger( args, regexp, result ) );
	},
	/**
	 * @param callback {function} 
	 */
	profile: function( callback ){
		callback = isFunction( callback ) ? callback : function(){};
		if( success !== true ) {
			callback.call(null);
			return;
		}
		callback.call( null, this.profiler( regexp ) );
	}
};
$.regexpr = new fn;
})(jQuery);